﻿using System;
using System.Collections.Generic;
using System.Threading;
using EmbeddedSharp.Core;
using Zove.Runtime.Core.InferencingStrategy;
using LibUnitConverter;

namespace Zove.Runtime.Core {
    /// <summary>
    /// The instance
    /// </summary>
    public class ZoveObject : IQueryable {
        /// <summary>
        /// Signales if the instance is being evaluating
        /// </summary>
        protected bool Evaluating;

        /// <summary>
        /// The runtime model
        /// </summary>
        protected RuntimeModel model;

        /// <summary>
        /// Default constructor
        /// </summary>
        public ZoveObject(ILanguageNature languageNature) {
            LanguageNature = languageNature;

            Formulas = new Dictionary<string, string>();
            Queue = new Dictionary<string, SolvedFactor>();
            InferenceStrategies = new Dictionary<string, IInferencingStrategy>
                                      {
                                          {"network", new NetworkingInferencingStrategy()},
                                          //this.inferenceStrategies.Add(new BasicInferencingStrategy());
                                          {"equation", new EquationInferencingStrategy(LanguageNature)}
                                          //{"nonlinear", new NonlinearInferencingStrategy()}
                                      };
        }

        ///// <summary>
        ///// Construtor with a class name
        ///// </summary>
        ///// <param name="className"></param>
        //public ModelInstance(string className, DefaultRuntimeModelLoader rml)
        //    : this() {
        //    this.ClassName = className;
        //    model = rml.LoadByClassName(className);
        //    model.Instance = this;

        //    if (model == null) {
        //        throw new NullReferenceException("Cannot find class " + className);
        //    }
        //}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="textContentProvider"></param>
        /// <param name="languageNature"></param>
        public ZoveObject(
            string name, ITextContentProvider textContentProvider, ILanguageNature languageNature)
            : this(languageNature) {
            Name = name;
            //ClassName = className;
            model = languageNature.ModelLoader.Load(textContentProvider); //.LoadByClassName(name, className));
            model.Instance = this;

            if (model == null) {
                throw new ClassNotFoundException("Cannot find class " + textContentProvider.Name);
            }
        }

        ///// <summary>
        ///// Build a model by an existed zoom class object
        ///// </summary>
        ///// <param name="c">zoom class object</param>
        ///// <param name="languageNature">the language nature</param>
        //public ModelInstance(ZoomClass c, ILanguageNature languageNature)
        //    : this()
        //{
        //    IClassFinder finder = ZoveDependencyManager.Instance.ClassFinder;
        //    IClassLoader cl = ZoveDependencyManager.Instance.ClassLoader;

        //    var ml = new DefaultRuntimeModelLoader();
        //    ml.ClassLoader = cl;

        //    Model = languageNature.ModelLoader.LoadByClass(c);
        //}

        /// <summary>
        /// The class name
        /// </summary>
        public string ClassName { get; set; }

        /// <summary>
        /// Language nature
        /// </summary>
        public ILanguageNature LanguageNature { get; set; }

        /// <summary>
        /// Inferencing strategies
        /// </summary>
        public Dictionary<string, IInferencingStrategy> InferenceStrategies { get; set; }

        /// <summary>
        /// The instance name
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// The solved factor queue
        /// </summary>
        public Dictionary<string, SolvedFactor> Queue { get; set; }

        /// <summary>
        /// The solved factor formula queue
        /// </summary>
        public Dictionary<string, string> Formulas { get; set; }

        /// <summary>
        /// Runtime model loader
        /// </summary>
        public IRuntimeModelLoader Loader { get; set; }

        /// <summary>
        /// The runtime model
        /// </summary>
        public RuntimeModel Model {
            get { return model; }
            set {
                model = value;
                model.Instance = this;
            }
        }

        /// <summary>
        /// Allocate a new model instance
        /// </summary>
        /// <param name="textContentProvider">text content provider</param>
        /// <param name="languageNature">language nature</param>
        /// <returns>the model instance</returns>
        public static ZoveObject Allocate(
            ITextContentProvider textContentProvider, ILanguageNature languageNature) {
            string source = textContentProvider.SourceReader.ReadToEnd();
            var i = new ZoveObject(languageNature)
                        {
                            Model = languageNature.ModelLoader.LoadBySource(source)
                        };
            i.Model.Instance = i;

            return i;
        }

        /// <summary>
        /// Initialize the factor
        /// </summary>
        /// <param name="factorName">the factor name</param>
        /// <param name="expr">the expression</param>
        public void Initialize(string factorName, string expr) {
            RuntimeFactor rf = Model.Factors[factorName];
            if (rf == null) {
                throw new FactorNotFoundException(factorName, Model);
            }

            string unit = rf.Unit;
            // length = 60 \m3
            int forwardSlash = expr.IndexOf('\\');
            if (forwardSlash > 0) {
                unit = expr.Split('\\')[1];
                if (Utils.IsIdentifier(unit)) {
                    expr = expr.Split('\\')[0];
                }
            }

            double result = Convert.ToDouble(Model.Evaluate(expr));
            if (unit != rf.Unit) {
                DefaultUnitConverter unitConverter = new DefaultUnitConverter();
                result = unitConverter.Convert(result, unit, rf.Unit);
            }

            rf.Initialize(result);
        }

        /// <summary>
        /// Reset all factors' status
        /// </summary>
        public void Reset() {
            foreach (RuntimeFactor rf in Model.Factors) {
                rf.Reset();
            }
        }

        /// <summary>
        /// Evaluate the instance
        /// </summary>
        public void Evaluate() {
            if (Evaluating) {
                throw new RuntimeException("Model is being evaluated.");
            }

            Evaluating = true;
            int oldCount;
            do {
                oldCount = Model.NumberOfAvailableFactors;
                foreach (IInferencingStrategy s in InferenceStrategies.Values) {
                    if (s.Enable) {
                        s.Infer(this);
                    }
                }
            } while (Model.NumberOfAvailableFactors > oldCount);
            Evaluating = false;
        }

        /// <summary>
        /// Assert if the expression is true.
        /// </summary>
        /// <param name="expression"></param>
        public object Evaluate(string expression) {
            return model.Evaluate(expression);
        }

        #region IQueryable Members

        public RuntimeFactorCollection Select(params string[] fields) {
            if (fields.Length == 1 && fields[0] == "*") {
                return Model.Factors;
            }

            RuntimeFactorCollection results = new RuntimeFactorCollection();
            foreach (string field in fields) {
                RuntimeFactor runtimeFactor = Model.Factors[field];
                if (runtimeFactor == null) {
                    throw new FactorNotFoundException(field);
                }

                results.Add(runtimeFactor);
            }

            return results;
        }

        /// <summary>
        /// Where conditions
        /// </summary>
        /// <param name="conditions"></param>
        /// <returns></returns>
        public IQueryable Where(params string[] conditions) {
            foreach (string condition in conditions) {
                string key = condition.Split('=')[0].Trim();
                string value = condition.Split('=')[1].Trim();
                Initialize(key, value);
            }

            return this;
        }

        #endregion
    }
}